#include "mediastreamer2/msfilter.h"
#include "mediastreamer2/msticker.h"
#include "mediastreamer2/msvideo.h"
#include "mediastreamer2/mswebcam.h"
#include "ohos_video_common.h"
#include "ohos_video_receiver.h"

#include <fstream>

#define UNDEFINED_ROTATION -1
#define DEFAULT_CLOCK_RATE 90000
#define DEFAULT_WIDTH 1280
#define DEFAULT_HEIGHT 720
#define DEFAULT_FPS 15

using namespace ohos;

struct OHOSCaptureVido {
	OHOSCaptureVido(MSFilter *f, MSWebCam *cam) : filter(f), webcam(cam), frame(0), fps(5) {
		ms_message("[Legacy Capture] Creating OHOSCaptureVido for ohos VIDEO capture filter");
		ms_mutex_init(&mutex, NULL);
		rotation = rotationSavedDuringVSize = UNDEFINED_ROTATION;
		allocator = ms_yuv_buf_allocator_new();
		ms_yuv_buf_allocator_set_max_frames(allocator, 2);
	};

	~OHOSCaptureVido() {
		if (frame != 0) {
			freeb(frame);
		}
		ms_yuv_buf_allocator_free(allocator);
		ms_mutex_destroy(&mutex);
	};

	MSFrameRateController fpsControl;
	MSAverageFPS averageFps;

	MSFilter *filter;
	MSWebCam *webcam;

	mblk_t *frame;
	float fps;
	MSVideoSize requestedSize, hwCapableSize, usedSize;
	ms_mutex_t mutex;
	int rotation, rotationSavedDuringVSize;
	int useDownscaling;
	char fps_context[64];
	MSYuvBufAllocator *allocator;
	VideoFrameReceiver *recevider;
};

static void compute_cropping_offsets(MSVideoSize hwSize, MSVideoSize outputSize, int *yoff, int *cbcroff);

void NV21ToI420(const uint8_t *nv21Data, uint8_t *i420Data, int width, int height) {
	int frameSize = width * height;
	int quarterFrameSize = frameSize / 4;

	memcpy(i420Data, nv21Data, frameSize);

	const uint8_t *uvSrc = nv21Data + frameSize;

	uint8_t *uDst = i420Data + frameSize;
	uint8_t *vDst = uDst + quarterFrameSize;

	for (int i = 0; i < quarterFrameSize; i++) {
		vDst[i] = uvSrc[i * 2];
		uDst[i] = uvSrc[i * 2 + 1];
	}
}

static void OnFrameCallBack(void *steam, void *buff, int length) {
	OHOSCaptureVido *d = (OHOSCaptureVido *)(intptr_t)steam;
	ms_message("reating OHOSCaptureVido OnFrameCallBack...........");

	MSPicture dest;
	mblk_t *m = ms_yuv_buf_alloc(&dest, DEFAULT_WIDTH, DEFAULT_HEIGHT);
	// NV21ToI420((uint8_t*)buff,dest.planes[0],DEFAULT_WIDTH,DEFAULT_HEIGHT);
	convert_nv21_to_yuv420p(&dest, (uint8_t *)buff);

	ms_mutex_lock(&d->mutex);

	if (d->frame) freemsg(d->frame);
	d->frame = m;

	ms_mutex_unlock(&d->mutex);
}

static void video_capture_init(MSFilter *f) {
	OHOSCaptureVido *d = new OHOSCaptureVido(f, 0);
	ms_message("[Legacy Capture] OHOSCaptureVido video_capture_init ohos VIDEO capture filter (%p)", d);
	FrameCallBack frameCallBack;
	frameCallBack.onFrameCallBack = OnFrameCallBack;
	auto videoFrameReceiver = VideoFrameReceiver::Create(frameCallBack);
	if (videoFrameReceiver) {
		d->recevider = std::move(videoFrameReceiver);
		d->recevider->InitCamera(DEFAULT_WIDTH, DEFAULT_HEIGHT);
	}
	f->data = d;
	d->recevider->setCallBackStream(d);
	ms_message("[Legacy Capture] OHOSCaptureVido video_capture_init end");
}

void video_capture_preprocess(MSFilter *f) {
	ms_message("[Legacy Capture] OHOSCaptureVido  Preprocessing of ohos VIDEO capture filter");
	OHOSCaptureVido *d = (OHOSCaptureVido *)(f->data);
	ms_mutex_lock(&d->mutex);

	ms_video_init_framerate_controller(&d->fpsControl, d->fps);
	ms_average_fps_init(&d->averageFps, d->fps_context);

	// ms_message("[Legacy Capture] OHOSCaptureVido Starting ohos camera '%d' (rotation:%d)",
	//            ((OHOSCaptureVido *)d->webcam->data)->id, d->rotation);

	int width = d->hwCapableSize.width == 0 ? DEFAULT_WIDTH : d->hwCapableSize.width;
	int height = d->hwCapableSize.height == 0 ? DEFAULT_HEIGHT : d->hwCapableSize.height;
	if (d->recevider) {
		//    d->recevider->InitCamera(width, height);
		d->recevider->setWidth(width);
		d->recevider->setHeight(height);
		d->recevider->StartCamera();
	}
	ms_message("[Legacy Capture] OHOSCaptureVidoss11 Preprocessing width:%d,height:%d", width, height);
	if (d->frame) {
		freemsg(d->frame);
		d->frame = NULL;
	}
	ms_mutex_unlock(&d->mutex);
}

static void video_capture_process(MSFilter *f) {
	ms_message("[Legacy Capture] OHOSCaptureVido processing of ohos VIDEO capture filter");
	OHOSCaptureVido *d = (OHOSCaptureVido *)(f->data);
	ms_mutex_lock(&d->mutex);

	ms_average_fps_activity(&d->averageFps, f->ticker->time, d->frame != 0);
	// If frame not ready, return
	if (d->frame == 0) {
		ms_mutex_unlock(&d->mutex);
		return;
	}

	mblk_set_timestamp_info(d->frame, f->ticker->time * 90);
	mblk_t *out = dupmsg(d->frame);
	ms_queue_put(f->outputs[0], out);
	ms_mutex_unlock(&d->mutex);
}

static void video_capture_postprocess(MSFilter *f) {
	ms_message("[Legacy Capture] OHOSCaptureVido Postprocessing of Android VIDEO capture filter");
	OHOSCaptureVido *d = (OHOSCaptureVido *)(f->data);
	ms_mutex_lock(&d->mutex);
	if (d->recevider) {
		d->recevider->StopCamera();
	}
	if (d->frame) {
		freemsg(d->frame);
		d->frame = NULL;
	}
	ms_mutex_unlock(&d->mutex);
}

static void video_capture_uninit(MSFilter *f) {
	ms_message("[Legacy Capture] OHOSCaptureVido Uninit of ohos VIDEO capture filter");
	OHOSCaptureVido *d = (OHOSCaptureVido *)(f->data);
	if (d->recevider) {
		d->recevider->destroy();
		d->recevider = nullptr;
	}
	delete d;
}

/************************ MS2 filter methods           ************************/

static int video_capture_set_fps(MSFilter *f, void *arg) {
	OHOSCaptureVido *d = (OHOSCaptureVido *)f->data;
	ms_mutex_lock(&d->mutex);
	d->fps = *((float *)arg);
	ms_message("OHOSCaptureVido ohos video_capture_set_fps expected=%f", d->fps);
	ms_video_init_framerate_controller(&d->fpsControl, d->fps);
	ms_average_fps_init(&d->averageFps, d->fps_context);
	ms_mutex_unlock(&d->mutex);
	return 0;
}

static int video_capture_get_fps(MSFilter *f, void *arg) {
	OHOSCaptureVido *d = (OHOSCaptureVido *)f->data;
	ms_mutex_lock(&d->mutex);
	*((float *)arg) = ms_average_fps_get(&d->averageFps);
	ms_mutex_unlock(&d->mutex);
	return 0;
}

static int video_capture_set_vsize(MSFilter *f, void *data) {
	OHOSCaptureVido *d = (OHOSCaptureVido *)f->data;
	ms_mutex_lock(&d->mutex);

	d->requestedSize = *(MSVideoSize *)data;

	// always request landscape mode, orientation is handled later
	if (d->requestedSize.height > d->requestedSize.width) {
		int tmp = d->requestedSize.height;
		d->requestedSize.height = d->requestedSize.width;
		d->requestedSize.width = tmp;
	}
	int count = d->recevider->getCameraCount();
	int rW = d->requestedSize.width > d->requestedSize.height ? d->requestedSize.width : d->requestedSize.height;
	int rH = d->requestedSize.width < d->requestedSize.height ? d->requestedSize.width : d->requestedSize.height;
	int req = rW * rH;
	int minDist = 0xffffffff;
	int useDownscale = 0;
	Camera_Profile *result = nullptr;
	ms_message("OHOSCaptureVido ohos video_capture_set_vsize getCameraCount count=%d", count);
	for (size_t i = 0; i < count; i++) {
		uint32_t size;
		Camera_Profile **profiles = d->recevider->getPreviewProfileByIndex(i, &size);
		if (profiles) {
			for (int j = 0; j < size; j++) {
				int w = profiles[j]->size.width;
				int h = profiles[j]->size.height;
				int dist = /*Math.abs*/ -1 * (req - w * h);
				if (((w >= rW && h >= rH) || (w >= rH && h >= rW)) && dist < minDist) {
					minDist = dist;
					result = profiles[j];
					useDownscale = 0;
				}

				int downScaleDist = /*Math.abs*/ -1 * (req - w * h / 4);
				if (((w / 2 >= rW && h / 2 >= rH) || (w / 2 >= rH && h / 2 >= rW)) && downScaleDist < minDist) {
					result = profiles[j];
					useDownscale = 0;
				}
				if (w == rW && h == rH) {
					result = profiles[j];
					useDownscale = 0;
					// break;
				}
			}
		}

		if (result == nullptr) {
			result = profiles[0];
		}
	}

	d->hwCapableSize.width = result->size.width;
	d->hwCapableSize.height = result->size.height;
	d->useDownscaling = useDownscale;
	ms_message("OHOSCaptureVidoss ohos video_capture_set_vsize result->size.width:%d,result->size.height:%d",
	           result->size.width, result->size.height);

	int rqSize = d->requestedSize.width * d->requestedSize.height;
	int hwSize = d->hwCapableSize.width * d->hwCapableSize.height;
	double downscale = d->useDownscaling ? 0.5 : 1;
	// if hw supplies a smaller resolution, modify requested size accordingly
	if ((hwSize * downscale * downscale) < rqSize) {
		// ms_message(
		//     "[Legacy Capture] OHOSCaptureVido Camera cannot produce requested resolution %dx%d, will supply smaller
		//     one: %dx%d\n", d->requestedSize.width, d->requestedSize.height, (int)(res[0] * downscale), (int)(res[1] *
		//     downscale));
		d->usedSize.width = (int)(d->hwCapableSize.width * downscale);
		d->usedSize.height = (int)(d->hwCapableSize.height * downscale);
	} else if ((hwSize * downscale * downscale) > rqSize) {
		if (d->requestedSize.width > d->hwCapableSize.width || d->requestedSize.height > d->hwCapableSize.height) {
			ms_message("[Legacy Capture] OHOSCaptureVido Camera cannot produce requested resolution %dx%d, will "
			           "capture a bigger one "
			           "(%dx%d)\n",
			           d->requestedSize.width, d->requestedSize.height, (int)(result->size.width * downscale),
			           (int)(result->size.height * downscale));
			d->usedSize.width = d->requestedSize.width = d->hwCapableSize.width;
			d->usedSize.height = d->requestedSize.height = d->hwCapableSize.height;
		} else {
			ms_message("[Legacy Capture] OHOSCaptureVido  Camera cannot produce requested resolution %dx%d, will "
			           "capture a bigger one "
			           "(%dx%d) and crop it to match encoder requested resolution\n",
			           d->requestedSize.width, d->requestedSize.height, (int)(result->size.width * downscale),
			           (int)(result->size.height * downscale));
			d->usedSize.width = d->requestedSize.width;
			d->usedSize.height = d->requestedSize.height;
		}
	} else {
		d->usedSize.width = d->requestedSize.width;
		d->usedSize.height = d->requestedSize.height;
	}
	ms_message("OHOSCaptureVido ohos video_capture_set_vsize end111");
	// is phone held |_ to cam orientation ?
	if (d->rotation == UNDEFINED_ROTATION) {
		if (d->rotation == UNDEFINED_ROTATION) {
			ms_error("[Legacy Capture] To produce a correct image, Mediastreamer MUST be aware of device's orientation "
			         "BEFORE calling 'configure_video_source'\n");
			ms_warning(
			    "[Legacy Capture] Capture filter do not know yet about device's orientation.\n"
			    "Current assumption: device is held perpendicular to its webcam (ie: portrait mode for a phone)\n");
			d->rotationSavedDuringVSize = 0;
		} else {
			d->rotationSavedDuringVSize = d->rotation;
		}
		// bool camIsLandscape = d->hwCapableSize.width > d->hwCapableSize.height;
		// bool useIsLandscape = d->usedSize.width > d->usedSize.height;

		// // if both are landscape or both portrait, swap
		// if (camIsLandscape == useIsLandscape) {
		// 	int t = d->usedSize.width;
		// 	d->usedSize.width = d->usedSize.height;
		// 	d->usedSize.height = t;
		// 	ms_message("[Legacy Capture] OHOSCaptureVido Swapped resolution width and height to : %dx%d\n",
		// d->usedSize.width, 	           d->usedSize.height);
		// }
	} else {
		d->rotationSavedDuringVSize = d->rotation;
	}

	if (d->usedSize.width != 0 && d->usedSize.height != 0) {
		ms_filter_notify(f, MS_CAMERA_PREVIEW_SIZE_CHANGED, &d->usedSize);
	}
	ms_message("OHOSCaptureVidoss hwCapableSize width:%d,hwCapableSize height:%d", d->hwCapableSize.width,
	           d->hwCapableSize.height);
	ms_message("OHOSCaptureVidoss requestedSize width:%d,requestedSize height:%d", d->requestedSize.width,
	           d->requestedSize.height);
	ms_message("OHOSCaptureVidoss usedSize width:%d,usedSize height:%d", d->usedSize.width, d->usedSize.height);

	ms_message("OHOSCaptureVido ohos video_capture_set_vsize end");
	ms_mutex_unlock(&d->mutex);
	return 0;
}

static int video_capture_get_vsize(MSFilter *f, void *data) {
	OHOSCaptureVido *d = (OHOSCaptureVido *)f->data;
	*(MSVideoSize *)data = d->usedSize;
	return 0;
}

static int video_capture_get_pix_fmt(BCTBX_UNUSED(MSFilter *f), void *data) {
	*(MSPixFmt *)data = MS_YUV420P;
	return 0;
}

static MSFilterMethod ohos_video_capture_methods[] = {
    {MS_FILTER_SET_FPS, &video_capture_set_fps},
    {MS_FILTER_GET_FPS, &video_capture_get_fps},
    {MS_FILTER_SET_VIDEO_SIZE, &video_capture_set_vsize},
    {MS_FILTER_GET_VIDEO_SIZE, &video_capture_get_vsize},
    {MS_FILTER_GET_PIX_FMT, &video_capture_get_pix_fmt},
    // {MS_VIDEO_DISPLAY_SET_NATIVE_WINDOW_ID, &video_set_native_preview_window}, // preview is managed by capture
    // filter {MS_VIDEO_DISPLAY_GET_NATIVE_WINDOW_ID, &video_get_native_preview_window},
    // {MS_VIDEO_CAPTURE_SET_DEVICE_ORIENTATION, &video_set_device_rotation},
    // {MS_VIDEO_CAPTURE_SET_AUTOFOCUS, &video_capture_set_autofocus},
    {0, 0}};

MSFilterDesc ms_ohos_video_capture_desc = {MS_OHOS_VIDEO_CAPTURE_ID,
                                           "MSOHOSVideoCapture",
                                           N_("A filter that captures OHOS video."),
                                           MS_FILTER_OTHER,
                                           NULL,
                                           0,
                                           1,
                                           video_capture_init,
                                           video_capture_preprocess,
                                           video_capture_process,
                                           video_capture_postprocess,
                                           video_capture_uninit,
                                           ohos_video_capture_methods};

MS_FILTER_DESC_EXPORT(ms_ohos_video_capture_desc)

/* Webcam methods */
static void video_capture_detect(MSWebCamManager *obj);
static void video_capture_cam_init(BCTBX_UNUSED(MSWebCam *cam)) {
	ms_message("[Legacy Capture] OHOSCaptureVido video_capture_cam_init filter cam init");
}

static MSFilter *video_capture_create_reader(MSWebCam *obj) {
	ms_message("[Legacy Capture] OHOSCaptureVido video_capture_create_reader Instanciating MS filter");

	MSFilter *lFilter = ms_factory_create_filter_from_desc(ms_web_cam_get_factory(obj), &ms_ohos_video_capture_desc);
	((OHOSCaptureVido *)(lFilter->data))->webcam = obj;
	ms_message("[Legacy Capture] OHOSCaptureVido video_capture_create_reader Instanciating  capture MS filter end");
	return lFilter;
}

MSWebCamDesc ms_cam_ohos_video_capture_desc = {"OhosVideoCapture", &video_capture_detect, &video_capture_cam_init,
                                               &video_capture_create_reader, NULL};

static void video_capture_detect(MSWebCamManager *obj) {
	ms_message("[Legacy Capture] OHOSCaptureVido video_capture_detect  Instanciating  MS filter");
	MSWebCam *cam = ms_web_cam_new(&ms_cam_ohos_video_capture_desc);
	// OHOSCaptureVido *c = new OHOSCaptureVido();
	// cam->data = c;
	cam->name = ms_strdup("Ohos video name");
	const char *facing = "Front";
	// if (c->frontFacing) {
	// 		facing = "Front";
	// 		frontFacingFound = true;
	// } else {
	// 		backFacingFound = true;
	// }

	char *idstring = bctbx_strdup_printf("%sFacingCamera%d", facing, 1);
	cam->id = ms_strdup(idstring);
	// bctbx_free(idstring);
	ms_web_cam_manager_prepend_cam(obj, cam);
	ms_message("[Legacy Capture] OHOSCaptureVido video_capture_detect Instanciating  MS filter end");
}

static void compute_cropping_offsets(MSVideoSize hwSize, MSVideoSize outputSize, int *yoff, int *cbcroff) {
	// if hw <= out -> return
	if (hwSize.width * hwSize.height <= outputSize.width * outputSize.height) {
		*yoff = 0;
		*cbcroff = 0;
		return;
	}

	int halfDiffW =
	    (hwSize.width - ((outputSize.width > outputSize.height) ? outputSize.width : outputSize.height)) / 2;
	int halfDiffH =
	    (hwSize.height - ((outputSize.width < outputSize.height) ? outputSize.width : outputSize.height)) / 2;

	*yoff = hwSize.width * halfDiffH + halfDiffW;
	*cbcroff = hwSize.width * halfDiffH * 0.5 + halfDiffW;
}